#include "StdAfx.h"
#include "WaveTrack.h"
#include "OcclusionXAPO.h"

typedef std::vector<XAUDIO2_EFFECT_DESCRIPTOR> vector;

WaveTrack::WaveTrack(TCHAR* FileName):pXAudio2(NULL), pMasteringVoice(NULL), pSourceVoice(NULL),
						   ovlCurrentRequest(OVERLAPPED() ), CurrentDiskReadBuffer(0),
						   buf(XAUDIO2_BUFFER() ), EffectCount(0), State(false)
{

	HRESULT hr;
	for(int i = 0; i <= MAX_BUFFER_COUNT - 1; i++)
		memset(buffers[i], 0, STREAMING_BUFFER_SIZE);


	// Initialize XAudio2
	CoInitializeEx( NULL, COINIT_MULTITHREADED );

	UINT32 flags = 0;
	#ifdef _DEBUG
		flags |= XAUDIO2_DEBUG_ENGINE;
	#endif

	if( FAILED( hr = XAudio2Create( &pXAudio2, flags ) ) )
    {
        wprintf( L"Failed to init XAudio2 engine: %#X\n", hr );
        CoUninitialize();
    }

	    
    // Create a mastering voice
    
    if( FAILED( hr = pXAudio2->CreateMasteringVoice( &pMasteringVoice ) ) )
    {
        wprintf( L"Failed creating mastering voice: %#X\n", hr );
        SAFE_RELEASE( pXAudio2 );
        CoUninitialize();
    }

		// Open the file
	    hFile = CreateFile(
		FileName,
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		0,
		NULL );

	if( INVALID_HANDLE_VALUE == hFile ) 
		wprintf(L"File could not open :%#X", HRESULT_FROM_WIN32( GetLastError() ) );

	if( INVALID_SET_FILE_POINTER == SetFilePointer( hFile, 0, NULL, FILE_BEGIN ) ) 
		wprintf(L"File could not open :%#X", HRESULT_FROM_WIN32( GetLastError() ) );

	//check the file type, should be fourccWAVE or 'XWMA'
	DWORD dwChunkSize;
	FindChunk(hFile,fourccRIFF,dwChunkSize, dwChunkPosition );
	DWORD filetype;
	ReadChunkData(hFile,&filetype,sizeof(DWORD),dwChunkPosition);
	if (filetype != fourccWAVE)
		wprintf(L"file type not is fourccWAVE");
	
	FindChunk(hFile,fourccFMT, dwChunkSize, dwChunkPosition );
	ReadChunkData(hFile, &wfx, dwChunkSize, dwChunkPosition );

	//fill out the audio data buffer with the contents of the fourccDATA chunk
	FindChunk(hFile,fourccDATA,dwChunkSize, dwChunkPosition );
	CurrentPosition = dwChunkPosition;
	cbWaveSize = CurrentPosition + dwChunkSize;
	
	
	
	// Create an XAudio2 voice to stream this wave
	if( FAILED( hr = pXAudio2->CreateSourceVoice( &pSourceVoice, &wfx.Format, 0, 1.0f, &voiceContext ) ) )
	{
		wprintf( L"\nError %#X creating source voice\n", hr );
	}

	// Create an overlapped structure and buffers to handle the async I/O
	ovlCurrentRequest.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

}

WaveTrack::~WaveTrack(void)
{
	pSourceVoice->Stop( 0 );
    pSourceVoice->DestroyVoice();

	pMasteringVoice->DestroyVoice();

    CloseHandle( hFile );
    CloseHandle( ovlCurrentRequest.hEvent );

	SAFE_RELEASE( pXAudio2 );
	CoUninitialize();
	
}

HRESULT WaveTrack::SendBufferToPlay(void) {
		HRESULT hr;

		if ( EndTrack() == S_OK )
			return S_FALSE;

		pSourceVoice->GetState( &state );
		if (state.BuffersQueued >= MAX_BUFFER_COUNT - 1)
			return S_FALSE;

		DWORD dwRead;
		DWORD cbValid = min( STREAMING_BUFFER_SIZE, cbWaveSize - CurrentPosition );

		if( 0 == ReadFile( hFile, buffers[CurrentDiskReadBuffer], cbValid, &dwRead, &ovlCurrentRequest ) )
			hr = HRESULT_FROM_WIN32( GetLastError() );

		typedef vector::const_iterator CI;
		for(CI p = VectorEffect.begin(); p != VectorEffect.end(); ++p);
			//if ((*p)->GetEffectState())
			//	(*p)->apply(buffers[CurrentDiskReadBuffer], cbValid, &wfx);

		ovlCurrentRequest.Offset += cbValid;

		//update the file position to where it will be once the read finishes
		CurrentPosition += cbValid;

		DWORD cb;
		//Use the GetOverlappedResult function to wait for the event that signals the read has finished.
        GetOverlappedResult( hFile, &ovlCurrentRequest, &cb, TRUE ); 

		/*
		while( pSourceVoice->GetState( &state ), state.BuffersQueued >= MAX_BUFFER_COUNT - 1)
		{
			WaitForSingleObject( voiceContext.hBufferEndEvent, INFINITE );
		}*/
			
		
		buf.AudioBytes = cbValid;
		buf.pAudioData = buffers[CurrentDiskReadBuffer];
		if( CurrentPosition >= cbWaveSize )



		{
			buf.Flags = XAUDIO2_END_OF_STREAM;
		}

		pSourceVoice->SubmitSourceBuffer( &buf );


		CurrentDiskReadBuffer++;
		CurrentDiskReadBuffer %= MAX_BUFFER_COUNT;

	

	/*
	while( pSourceVoice->GetState( &state ), state.BuffersQueued > 0 )
	{
		WaitForSingleObject( voiceContext.hBufferEndEvent, INFINITE );
	}
	*/
	
	return S_OK;
}



HRESULT WaveTrack::EndTrack(void) {
	if (CurrentPosition >= cbWaveSize)
		return S_OK;
	else
		return S_FALSE;
}


void WaveTrack::Play(void)
{	
	if (!VectorEffect.empty()) {
		chain.EffectCount = EffectCount;
		chain.pEffectDescriptors = &VectorEffect[0];
		pSourceVoice->SetEffectChain(&chain);
	}
	State = true;
	pSourceVoice->Start( 0, 0 );

	return;
}

void WaveTrack::Pause(void)
{
	State = false;
	pSourceVoice->Stop();
	return;
}

void WaveTrack::SetVolume(float volume) 
{
	pSourceVoice->SetVolume(volume);
	
	return;
}

void WaveTrack::SetVolume(float* pVolume, int channels) 
{
	pSourceVoice->SetChannelVolumes(channels, pVolume);
	
	return;
}

void WaveTrack::GetVolume(float* pVolume)
{
	pSourceVoice->GetVolume(pVolume);
	return;
}

void WaveTrack::Rewind(float time) {

	DWORD NewOffset = time *wfx.Format.nSamplesPerSec * wfx.Format.nChannels;	
	pSourceVoice->FlushSourceBuffers();
	ovlCurrentRequest.Offset = NewOffset;
	CurrentPosition = dwChunkPosition + NewOffset;
	//SendBufferToPlay();
	return;
}

DWORD WaveTrack::CurrentPositionSec(void) {
	return (CurrentPosition - dwChunkPosition)/(wfx.Format.nSamplesPerSec * wfx.Format.nChannels);
}

DWORD WaveTrack::LengthTrackSec(void) {
	return cbWaveSize / (wfx.Format.nSamplesPerSec * wfx.Format.nChannels);
}


HRESULT WaveTrack::AddEffect(EffectXAPO* pNewEffect) {
	VectorEffect.push_back( *(pNewEffect->GetEffectDescriptor() ) );
	pNewEffect->SetEffectNumber(EffectCount);
	EffectCount++;
	return S_OK; 

	
}

HRESULT WaveTrack::RemoveEffect(int number) {
	vector::iterator p;
	*p = VectorEffect[number];
	VectorEffect.erase(p);

	return S_OK;
}


HRESULT WaveTrack::FindChunk(HANDLE hFile, DWORD fourcc, DWORD & dwChunkSize, DWORD & dwChunkDataPosition)
{
    HRESULT hr = S_OK;
    if( INVALID_SET_FILE_POINTER == SetFilePointer( hFile, 0, NULL, FILE_BEGIN ) )
        return HRESULT_FROM_WIN32( GetLastError() );

    DWORD dwChunkType;
    DWORD dwChunkDataSize;
    DWORD dwRIFFDataSize = 0;
    DWORD dwFileType;
    DWORD bytesRead = 0; 
    DWORD dwOffset = 0;

    while (hr == S_OK)
    {
        DWORD dwRead;
        if( 0 == ReadFile( hFile, &dwChunkType, sizeof(DWORD), &dwRead, NULL ) )
            hr = HRESULT_FROM_WIN32( GetLastError() );

        if( 0 == ReadFile( hFile, &dwChunkDataSize, sizeof(DWORD), &dwRead, NULL ) )
            hr = HRESULT_FROM_WIN32( GetLastError() );

        switch (dwChunkType)
        {
        case fourccRIFF:
            dwRIFFDataSize = dwChunkDataSize;
            dwChunkDataSize = 4;
            if( 0 == ReadFile( hFile, &dwFileType, sizeof(DWORD), &dwRead, NULL ) )
                hr = HRESULT_FROM_WIN32( GetLastError() );
            break;

        default:
            if( INVALID_SET_FILE_POINTER == SetFilePointer( hFile, dwChunkDataSize, NULL, FILE_CURRENT ) )
            return HRESULT_FROM_WIN32( GetLastError() );            
        }

        dwOffset += sizeof(DWORD) * 2;
        
        if (dwChunkType == fourcc)
        {
            dwChunkSize = dwChunkDataSize;
            dwChunkDataPosition = dwOffset;
            return S_OK;
        }

        dwOffset += dwChunkDataSize;
        
        if (bytesRead >= dwRIFFDataSize) return S_FALSE;

    }

    
    return S_OK;
    
}

HRESULT WaveTrack::ReadChunkData(HANDLE hFile, void * buffer, DWORD buffersize, DWORD bufferoffset)
{
    HRESULT hr = S_OK;
    if( INVALID_SET_FILE_POINTER == SetFilePointer( hFile, bufferoffset, NULL, FILE_BEGIN ) )
        return HRESULT_FROM_WIN32( GetLastError() );
    DWORD dwRead;
    if( 0 == ReadFile( hFile, buffer, buffersize, &dwRead, NULL ) )
        hr = HRESULT_FROM_WIN32( GetLastError() );
    return hr;
}
	


